﻿using System;
using System.Collections.Generic;
using System.Text;
using CommonClasses;
using Eco.ObjectRepresentation;
using Descriptions_DomainModel.Model;
using System.IO;
using Descriptions_PinsDescription.Model;
using System.Xml.Serialization;
using System.Net.Sockets;
using WinDerivate;

namespace StoveSupervisor
{

    /// <summary>
    /// Управляющая сборка, осуществляющая работу с MP11 по виртуальным пинам
    /// </summary>
    public class StoveSupervisor : IConnection
    {
        private const string PIN_QUERY = "Stove.Query";

        private const string PIN_RUN_CONTROL = "Stove.RunControl";

        private const string PIN_STOP_CONTROL = "Stove.StopControl";

        private const string PIN_SENSOR = "KE2_ADC";

        private const string PIN_CONTROL_STATE = "Stove.ControlState";

        private const string METHOD_STOVE_ON = "On";

        public struct Config
        {
            public int delta;
            public string hostname;
            public int port;
        }

        /// <summary>
        /// Таблица пересчета из значений АЦП (0...1024) в милливольты или градусы
        /// Пересчет в соответствии с калибровочной таблицей термопары (из файла)
        /// </summary>
        CalibrationTableHolder calibrationTableHolder;


        #region Private fields

        /// <summary>
        /// Диспетчер соединений
        /// </summary>
        private IConnection connectionDispatcher;

        /// <summary>
        /// Провайдер сервисов ECO
        /// </summary>
        private IEcoServiceProvider serviceProvider;

        /// <summary>
        /// Список выходных пинов
        /// </summary>
        private List<int> outPins;

        string name;

        #region Pins

        /// <summary>
        /// Пин включения/отключения печки
        /// </summary>
        private int pinPower;

        /// <summary>
        /// Пин для натыркивания нам значения
        /// </summary>
        private int pinQuery;

        /// <summary>
        /// Пин активации контроля за температурой
        /// </summary>
        private int pinRunControl;

        /// <summary>
        /// Пин деактивации контроля за температурой
        /// </summary>
        private int pinStopControl;

        /// <summary>
        /// Пин датчика, по которому KE шлет усредненные значения, которое мы устанавливаем в statePin
        /// </summary>
        private int pinSensor;

        /// <summary>
        /// Пин включенности/отключенности контроля температуры
        /// </summary>
        /// <remarks>
        /// Нужно только для того, чтобы анимация
        /// </remarks>
        private int pinControlState;

        /// <summary>
        /// Пин состояния печки (значение температуры)
        /// </summary>
        private StatePinDefinition statePin;

        #endregion

        /// <summary>
        /// Включен ли режим контроля
        /// </summary>
        private bool isControlOn;        

        /// <summary>
        /// Натырканная (запрошенная) температура
        /// </summary>
        private int queryTemp;

        /// <summary>
        /// Целевая температура
        /// </summary>
        private int targetTemp;

        #region Private UDP fields
        /// <summary>
        /// UDP сервер для отправки температуры на внешнюю программу с графиком 
        /// </summary>
        private UdpClient udpClient;

        /// <summary>
        /// Конфиг супервизора
        /// </summary>
        private Config config = new Config();

        /// <summary>
        /// Служебная переменная для реализации выборочной отсылки отсчетов по UDP
        /// </summary>
        private int udp_index = 0;
        /// <summary>
        /// Каждый udp_send_factor'ый отсчет отсылается по UDP 
        /// Это нужно, чтобы клиент успевал синхронно считывать отсчеты
        /// </summary>
        private int udp_send_factor = 5;
        #endregion
        #endregion

        /// <param name="serviceProvider">Провайдер сервисов ECO</param>
        /// <param name="nodeGroup">группа узлов, которая будет управляться данным супервизором</param>
        /// <param name="connectionDispatcher">Диспетчер подключений</param>        
        public StoveSupervisor(IEcoServiceProvider serviceProvider, NodeGroup nodeGroup, IConnection connectionDispatcher, PinsDescriptionTable pinTable)
        {
            calibrationTableHolder = CalibrationTableHolder.Load(".\\xa.xls");

            var node = nodeGroup.Nodes[0];

            name = Path.GetFileNameWithoutExtension(node.Name);

            if (connectionDispatcher == null)
                throw new NullReferenceException("StoveSupervisor: ConnectionDispatcher cannot be null!");

            this.serviceProvider = serviceProvider;
            this.connectionDispatcher = connectionDispatcher;

            statePin = new StatePinDefinition(node.StatePinNumber, node.StatePinMin, node.StatePinMax, 0);
            outPins = new List<int>();
            foreach (Method meth in node.Methods)
            {
                if (!outPins.Contains(meth.VirtualPinOut))
                    outPins.Add(meth.VirtualPinOut);
            }

            foreach (var pd in pinTable.PinTable)
                switch (pd.Descriptor)
                {
                    case PIN_RUN_CONTROL:
                        pinRunControl = pd.VirtualPin;
                        break;

                    case PIN_STOP_CONTROL:
                        pinStopControl = pd.VirtualPin;
                        break;

                    case PIN_QUERY:
                        pinQuery = pd.VirtualPin;
                        break;

                    case PIN_SENSOR:
                        pinSensor = pd.VirtualPin;
                        break;

                    case PIN_CONTROL_STATE:
                        pinControlState = pd.VirtualPin;
                        break;
                }

            foreach (var meth in node.Methods)
                if (meth.Name == METHOD_STOVE_ON)
                {
                    pinPower = meth.VirtualPinOut;
                    break;
                }
            
            //config.delta = 25;
            //config.hostname = "localhost";
            //config.port = 50001;            

            XmlSerializer xmlser = new XmlSerializer(typeof(Config));
            //TextWriter writer = new StreamWriter(nodeGroup.AssemblyConfigFileName);
            TextReader reader = new StreamReader(nodeGroup.AssemblyConfigFileName);
            config = (Config)xmlser.Deserialize(reader);
            //Console.WriteLine("StoveDelta = " + delta);
            //xmlser.Serialize(writer, config);
            //writer.Close();
            reader.Close();

            udpClient = new UdpClient(config.hostname, config.port);
            //udpClient.DontFragment = true;

            SubscribeToConnections();

            Console.WriteLine("StoveSupervisor created");
        }


        #region Configuring

        /// <summary>
        /// Подписаться на изменения пина у соединений
        /// </summary>
        private void SubscribeToConnections()
        {
            connectionDispatcher.OnPinStateChanged += new PinStateChangedHandler(OnPinStateChangedHandler);
        }

        /// <summary>
        /// Снять подписку на изменение пина у соединения
        /// </summary>
        private void DetachSubscription()
        {
            connectionDispatcher.OnPinStateChanged -= new PinStateChangedHandler(OnPinStateChangedHandler);
        }

        #endregion


        #region IConnection Members

        /// <summary>
        /// Событие: изменился входной пин
        /// </summary>
        public event PinStateChangedHandler OnPinStateChanged;

        public void OnPin(int virtualPinOut, int signal)
        {
            if (virtualPinOut == pinQuery)
                queryTemp += signal;
            else if (virtualPinOut == pinRunControl)
            {
                isControlOn = (signal == 1);
                targetTemp = queryTemp;
                queryTemp = 0;
            }
            else if (virtualPinOut == pinStopControl)
            {
                if (signal == 1)
                    isControlOn = false;
                targetTemp = 0;
                queryTemp = 0;
            }
            else if (outPins.Contains(virtualPinOut))
            {
                Console.WriteLine("{0}.OnPin({1},{2})", name, virtualPinOut, signal);
                connectionDispatcher.OnPin(virtualPinOut, signal);
            }
            //else Console.WriteLine("{0}:!(outPins.Contains(virtualPinOut)), {1}", name, virtualPinOut.ToString());
        }

        public void FlushPins()
        {
            Console.WriteLine("StoveSupervisor.FlushPins ");
            queryTemp = 0;
        }

        public void Dispose()
        {
            Console.WriteLine("StoveSupervisor.Dispose ");
            DetachSubscription();
            udpClient.Close();
        }

        #endregion

        /// <summary>
        /// Обработка сигналов с датчиков (обрабатываются только еденицы)
        /// </summary>
        /// <param name="virtualPin">изменившийся вирт. пин</param>
        /// <param name="newState">значение на пине</param>
        private void OnPinStateChangedHandler(int virtualPin, int newState)
        {
            if (virtualPin != pinSensor)
                return;
            //пересчитаем значение в градусах 
            var val = calibrationTableHolder.computeTemp(newState);
            statePin.Value = (int) Math.Max(statePin.Min, Math.Min(val, statePin.Max));


            Console.WriteLine("<{0}.OnPinStateChanged>:  ({1},{2})", name, statePin.PinNumber, statePin.Value);

            if (statePin.Value >= targetTemp + config.delta)
            {
                OnPin(pinPower, 0);
                OnPinStateChanged(pinControlState, 0);
            }
            else if (statePin.Value <= targetTemp - config.delta)
            {                
                OnPinStateChanged(pinControlState, 1);
                OnPin(pinPower, 1);
            }
            if (OnPinStateChanged != null)
            {
                OnPinStateChanged(statePin.PinNumber, statePin.Value);
                
                udp_index = udp_index + 1;
                if (udp_index % udp_send_factor == 0)
                {
                    var datagram = ASCIIEncoding.ASCII.GetBytes(newState.ToString());
                    udpClient.Send(datagram, datagram.Length);
                    udp_index = 0;
                }
            }
            //else нас отписали на событие, т.к. реальное соединение не используется
        }
    }

}
